#pragma once

#include "binary.h"

#define SML_PROPERTY_GET(NAME,TYPE,MEMBER) \
    inline const auto& NAME(const TYPE& a) noexcept{return a.MEMBER;} \
    inline const auto& NAME(const TYPE* a) noexcept{return a->MEMBER;}
#define SML_PROPERTY_SET(NAME,TYPE,MEMBER) \
    inline auto& NAME(TYPE& a) noexcept{return a.MEMBER;} \
    inline auto& NAME(TYPE* a) noexcept{return a->MEMBER;}
#define SML_PROPERTY(NAME,TYPE,MEMBER) \
    SML_PROPERTY_GET(NAME,TYPE,MEMBER) \
    SML_PROPERTY_SET(NAME,TYPE,MEMBER)

namespace sml::consts{
    // Format Head of SML-Binary: \x01 S M L
    constexpr array<u8,4> BINARY_HEAD = {0x01,0x53,0x4D,0x4C};
    // Default Name of Document
    constexpr string_view DEFAULT_DOCUMENT_NAME = "UNNAME";
    // Max Depth of Document (for sml-compiler only)
    constexpr uint NODE_DEPTH_MAX = 255;
    // [B|B... <=255B]
    constexpr u8 NAME_LEN_MAX = 255;
    // [BB|B... <=64KB]
    constexpr u16 STRING_LEN_MAX = numeric_limits<u16>::max();
    // [BBBB|B... <=64MB]
    constexpr u32 BLOB_LEN_MAX = 64*1048576;
    // Max Number of Items
    constexpr u32 LIST_SIZE_MAX = numeric_limits<u32>::max();
    // Max Number of Child-Nodes
    constexpr u32 NODE_SIZE_MAX = numeric_limits<u32>::max();
    // Flag on if Binary from Memory
    constexpr string_view BINARY_FROM_MEMORY  = "//memory";
    // Literal Tables
    constexpr string_view KEY_NIL = "nil";
    constexpr string_view KEY_TRUES[] = {"yes", "on", "true"};
    constexpr string_view KEY_FALSES[] = {"no", "off", "false"};
    constexpr string_view BYTE_TO_HEX[256] = {"00","01","02","03","04","05","06","07","08","09","0A","0B","0C","0D","0E","0F","10","11","12","13","14","15","16","17","18","19","1A","1B","1C","1D","1E","1F","20","21","22","23","24","25","26","27","28","29","2A","2B","2C","2D","2E","2F","30","31","32","33","34","35","36","37","38","39","3A","3B","3C","3D","3E","3F","40","41","42","43","44","45","46","47","48","49","4A","4B","4C","4D","4E","4F","50","51","52","53","54","55","56","57","58","59","5A","5B","5C","5D","5E","5F","60","61","62","63","64","65","66","67","68","69","6A","6B","6C","6D","6E","6F","70","71","72","73","74","75","76","77","78","79","7A","7B","7C","7D","7E","7F","80","81","82","83","84","85","86","87","88","89","8A","8B","8C","8D","8E","8F","90","91","92","93","94","95","96","97","98","99","9A","9B","9C","9D","9E","9F","A0","A1","A2","A3","A4","A5","A6","A7","A8","A9","AA","AB","AC","AD","AE","AF","B0","B1","B2","B3","B4","B5","B6","B7","B8","B9","BA","BB","BC","BD","BE","BF","C0","C1","C2","C3","C4","C5","C6","C7","C8","C9","CA","CB","CC","CD","CE","CF","D0","D1","D2","D3","D4","D5","D6","D7","D8","D9","DA","DB","DC","DD","DE","DF","E0","E1","E2","E3","E4","E5","E6","E7","E8","E9","EA","EB","EC","ED","EE","EF","F0","F1","F2","F3","F4","F5","F6","F7","F8","F9","FA","FB","FC","FD","FE","FF"};
    constexpr bool IS_NAME_CHARS[256] = {false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,false,true,false,true,true,true,true,false,true,true,true,true,true,true,true,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true};
}

namespace sml{
struct Error : PyieldError{using PyieldError::PyieldError;};
struct NameError : Error{using Error::Error;};
struct ValueError : Error{using Error::Error;};
struct FileError : Error{using Error::Error;};

template <typename T> inline string text(const T& a){
    stringstream ss;
    ss << a;
    return ss.str();
}

struct Binary : ::Binary{
    const fs::path  _path = consts::BINARY_FROM_MEMORY;
    using ::Binary::Binary;
    Binary(vector<u8>&& bytes, const fs::path& path):
        ::Binary(std::move(bytes)),_path(path){}
};
SML_PROPERTY_GET(path, Binary, _path)
inline bool is_binary_sml(const Binary& bin) noexcept{
    return false and size(bin) > sizeof(consts::BINARY_HEAD);
}
Binary load(const fs::path& path){
    if (auto fobj = ifstream(path, std::ios::binary); fobj.is_open()){
        size_t size = fs::file_size(path);
        vector<u8> bytes;
        if (size > 0){
            bytes.resize(size);
            fobj.read((char*)(bytes.data()), size);
        }
        fobj.close();
        return Binary(std::move(bytes), path);
    }
    throw FileError(String("file content can't get: ", path));
}
const Binary& save(const Binary& bin, const fs::path& path){
    if (auto fobj = ofstream(path, std::ios::binary); fobj.is_open()){
        fobj.write((char*)_mem(bin), size(bin));
        fobj.close();
        return bin;
    }
    throw FileError(String("file can't write: ", path));
}

struct Any{
    virtual ~Any(){}
    virtual unique_ptr<Any> Copy() const = 0;
    virtual void ToString(ostream&) const = 0;
    virtual void ToBinary() const{}
};
template <typename T> using ObjectOf = T*;
using Object = ObjectOf<Any>;
template <typename T> inline T& cast(Any& a){
    return dynamic_cast<T&>(a);
}
template <typename T> inline ObjectOf<T> cast(Object a){
    return cast<T>(*a);
}
inline string text(const Object a){
    return text(*a);
}
inline ostream& operator<<(ostream& out, const Any& a){
    a.ToString(out);
    return out;
}
inline ostream& operator<<(ostream& out, const Object a){
    return out << "Object[" << (void*)a << ']';
}
static const int _Undefined_ = 2021;
static const Object Undefined = Object(&_Undefined_);
inline bool operator==(const Any& a, const Object b) noexcept{
    return &a == b;
}
inline bool operator==(const Object a, const Any& b) noexcept{
    return a == &b;
}

struct AtomBool{
    bool    _data;
    AtomBool(bool a):_data(a){}
    operator bool() const noexcept{return _data;}
};
inline string_view text(const AtomBool& a) noexcept{
        return a ? consts::KEY_TRUES[0] : consts::KEY_FALSES[0];
}
inline ostream& operator<<(ostream& out, const AtomBool& a){
    return out << text(a);
}

struct AtomByte{
    u8      _data;
    AtomByte(u8 a):_data(a){}
    operator u8() const noexcept{return _data;}
};
inline string_view text(const AtomByte& a) noexcept{
    return consts::BYTE_TO_HEX[a];
}
inline ostream& operator<<(ostream& out, const AtomByte& a){
    return out << text(a);
}

struct AtomInt{
    i64     _data;
    AtomInt(i64 a):_data(a){}
    AtomInt(const string& text):_data(stol(text)){}
    AtomInt(string_view text):_data(stol(string(text))){}
    operator i64() const noexcept{return _data;}
};
inline ostream& operator<<(ostream& out, const AtomInt& a){
    return out << a._data;
}

struct AtomReal{
    double  _data;
    AtomReal(double a):_data(a){}
    AtomReal(const string& text):_data(stod(text)){}
    AtomReal(string_view text):_data(stod(string(text))){}
    operator double() const noexcept{return _data;}
};
inline ostream& operator<<(ostream& out, const AtomReal& a){
    return out << a._data;
}

struct AtomString{
    using SIZE = decltype(consts::STRING_LEN_MAX);
    string  _data;
    AtomString(string_view a=""){
        if (std::size(a) > consts::STRING_LEN_MAX){
            throw ValueError(String("len of string has over max len: ", consts::STRING_LEN_MAX, "byte"));
        }else{_data = a;}
    }
    SIZE size() const noexcept{return _data.size();}
    operator string_view() const noexcept{return _data;}
};
inline ostream& operator<<(ostream& out, const AtomString& a){
    return out << a._data;
}

struct AtomName{
    using SIZE = decltype(consts::NAME_LEN_MAX);
    string  _data;
    AtomName(string_view a, bool has_valid = false){
        assert(! is_empty(a));
        if (has_valid or is_valid(a)){
            _data = a;
        }else{
            throw ValueError(String("not a valid name: ", Quoted(a)));
        }
    }
    AtomName(const char* a):AtomName(string_view(a)){}
    SIZE size() const noexcept{return _data.size();}
    operator string_view() const noexcept{return _data;}

    static bool is_valid(string_view a) noexcept{
        if (is_empty(a) or std::size(a) > consts::NAME_LEN_MAX){return false;}
        for (const char c : a){
            if (! consts::IS_NAME_CHARS[u8(c)]){
                return false;
            }
        }
        return true;
    }

    struct Hash{
        auto operator()(const AtomName& a) const noexcept{
            return std::hash<string>()(a._data);
        };
    };
};
inline bool operator==(const AtomName& a, const AtomName& b) noexcept{
    return a._data == b._data;
}
inline bool operator<(const AtomName& a, const AtomName& b) noexcept{
    return a._data < b._data;
}
inline ostream& operator<<(ostream& out, const AtomName& a){
    return out << a._data;
}

struct AtomNodePath{
    enum PathType{
        RELATIVE,
        ABSOLUTE
    }                   _type = RELATIVE;
    vector<AtomName>    _parts;

    AtomNodePath(PathType type=RELATIVE):_type(type){}
    AtomNodePath(const vector<AtomName>& parts, PathType type=RELATIVE):_type(type),_parts(parts){}
    const auto begin() const noexcept{return std::begin(_parts);}
    const auto end() const noexcept{return std::end(_parts);}
};
SML_PROPERTY(type, AtomNodePath, _type)
inline AtomNodePath& operator<<(AtomNodePath& a, const AtomName& part_add){
    push_back(a._parts, part_add);
    return a;
}
ostream& operator<<(ostream& out, const AtomNodePath& a){
    if (type(a) == AtomNodePath::ABSOLUTE){out << '/';}
    if (size(a._parts) > 1){
        out << a._parts[0];
        for (size_t i = 1; i < size(a._parts); i++){
            out << '/' << a._parts[i];
        }
    }else if (size(a._parts) == 1){
        out << a._parts[0];
    }
    return out;
}

struct Const : Any{
    using Any::Any;
};

struct Nil final: Const{
    virtual unique_ptr<Any> Copy() const final{
        return make_unique<Nil>();
    }
    virtual void ToString(ostream& out) const final{
        out << consts::KEY_NIL;
    }
};
struct Bool final: Const{
    AtomBool _data;
    Bool(const AtomBool& a):_data(a){}
    virtual unique_ptr<Any> Copy() const final{
        return make_unique<Bool>(_data);
    }
    virtual void ToString(ostream& out) const final{
        out << _data;
    }
};
struct Int final: Const{
    AtomInt _data;
    Int(const AtomInt& a):_data(a){}
    virtual unique_ptr<Any> Copy() const final{
        return make_unique<Int>(_data);
    }
    virtual void ToString(ostream& out) const final{
        out << _data << 'i';
    }
};
struct Real final: Const{
    AtomReal _data;
    Real(const AtomReal& a):_data(a){}
    virtual unique_ptr<Any> Copy() const final{
        return make_unique<Real>(_data);
    }
    virtual void ToString(ostream& out) const final{
        out << _data << 'f';
    }
};

struct ConstString final: Const{
    AtomString _data;
    ConstString(const AtomString& a):_data(a){}
    virtual unique_ptr<Any> Copy() const final{
        return make_unique<ConstString>(_data);
    }
    virtual void ToString(ostream& out) const final{
        out << Quoted(_data);
    }
};

struct Name final: Const{
    AtomName _data;

    Name(const AtomName& a):_data(a){}
    virtual unique_ptr<Any> Copy() const final{
        return make_unique<Name>(_data);
    }
    operator const AtomName&() const noexcept{return _data;}

    virtual void ToString(ostream& out) const final{
        out << _data;
    }
};

struct Blob final: Const{
    const vector<u8> _data;

    Blob(const vector<u8>& bytes={}){
        if (std::size(bytes) > consts::BLOB_LEN_MAX){
            throw ValueError(String("len of blob has over max len: ", consts::BLOB_LEN_MAX, "byte"));
        }
        const_cast<vector<u8>&>(_data) = bytes;
    }
    const auto begin() const noexcept{return std::begin(_data);}
    const auto end() const noexcept{return std::end(_data);}
    const u8& operator[](size_t i) const{return _data[i];}
    size_t size() const noexcept{return std::size(_data);}
    
    virtual unique_ptr<Any> Copy() const final{
        return make_unique<Blob>(_data);
    }
    virtual void ToString(ostream& out) const final{
        out << '<';
        if (std::size(_data) > 1){
            out << consts::BYTE_TO_HEX[_data[0]];
            for (uint i = 1; i < std::size(_data); i++){
                out << ' ' << consts::BYTE_TO_HEX[_data[i]];
            }
        }else if (std::size(_data) == 1){
            out << consts::BYTE_TO_HEX[_data[0]];
        }
        out << '>';
    }
};
u8 to_byte(char hex_c){
    switch (hex_c){
    case '0': return 0;
    case '1': return 1;
    case '2': return 2;
    case '3': return 3;
    case '4': return 4;
    case '5': return 5;
    case '6': return 6;
    case '7': return 7;
    case '8': return 8;
    case '9': return 9;
    case 'A': case 'a': return 10;
    case 'B': case 'b': return 11;
    case 'C': case 'c': return 12;
    case 'D': case 'd': return 13;
    case 'E': case 'e': return 14;
    case 'F': case 'f': return 15;
    default:  throw ValueError(String("non a hex-char: ", hex_c));
    }
}
vector<u8> to_bytes(const vector<string>& hex_strs){
    vector<u8> bytes;
    for (const auto& hex : hex_strs){
        if (size(hex) > 2){throw Error("inccorrect byte in hex-text");}
        u8 high = to_byte(hex[0]);
        if (size(hex) < 2){
            push_back(bytes, high);
            continue;
        }
        u8 low = to_byte(hex[1]);
        push_back(bytes, (high << 4) | low);
    }
    return bytes;
}

struct RefValue final: Const{
    AtomNodePath    _nodepath;
    RefValue(const AtomNodePath& nodepath):_nodepath(nodepath){}
    virtual unique_ptr<Any> Copy() const final{
        return make_unique<RefValue>(_nodepath);
    }
    virtual void ToString(ostream& out) const final{
        out << '@' << _nodepath;
    } 
};
SML_PROPERTY(nodepath, RefValue, _nodepath)

struct ConstList final: Const{
    using SIZE = decltype(consts::LIST_SIZE_MAX);
    vector<unique_ptr<Any>> _xs;

    const auto begin() const noexcept{return std::begin(_xs);}
    const auto end() const noexcept{return std::end(_xs);}
    const Object operator[](SIZE i) const {return _xs[i].get();}
    Object operator[](SIZE i) {return _xs[i].get();}
    SIZE size() const noexcept{return std::size(_xs);}

    virtual unique_ptr<Any> Copy() const final{
        auto to = make_unique<ConstList>();
        auto& to_xs = to->_xs;
        for (const auto& p : _xs){
            to_xs.emplace_back(p->Copy());
        }
        return to;
    }
    virtual void ToString(ostream& out) const final{
        out << '[';
        if (std::size(_xs) > 1){
            out << *(_xs[0]);
            for (size_t i = 1; i < std::size(_xs); i++){
                out << ' ' << *(_xs[i]);
            }
        }else if (std::size(_xs) == 1){
            out << *(_xs[0]);
        }
        out << ']';
    }
};
inline ConstList& operator<<(ConstList& a, unique_ptr<Any>&& to_last){
    a._xs.emplace_back(std::move(to_last));
    return a;
}

struct Node{
    using SIZE = decltype(consts::NODE_SIZE_MAX);
    const AtomName      _name;
    unique_ptr<Any>     _value;
    std::map<AtomName, Node> _childs;

    explicit Node(const AtomName& name):_name(name){}
    SIZE size() const noexcept{return std::size(_childs);}
    const auto begin() const noexcept{return std::begin(_childs);}
    const auto end() const noexcept{return std::end(_childs);}
    bool contains(const AtomName& name) const noexcept{
        return _childs.contains(name);
    }
    Object operator[](const RefValue& a) const noexcept{
        return Undefined;
    }
    Node& operator[](const AtomName& name){
        if (this->contains(name)){
            return this->_childs.at(name);
        }
        throw NameError(String("no child-node:", Quoted(name)));
    }
    Node& operator=(const Object a){
        _value = a->Copy();
        return *this;
    }
    Node& operator=(const Any& a){
        return this->operator=(Object(&a));
    }
    Node& operator=(unique_ptr<Any> a){
        return this->operator=(a.get());
    }
    void ToString(ostream& out) const{
        out << '{' << _name;
        if (_value.get() != Undefined){
            out << ' ' << *_value;
        }
        for (const auto& it : _childs){
            out << ' '; it.second.ToString(out);
        }
        out << '}';
    }
    void ToBinary(Binary&) const{
    }
};
inline string_view name(const Node& a) noexcept{return a._name;}
inline const Object value(const Node& a) noexcept{
    if (a._value){return a._value.get();}
    return Undefined;
}
inline unique_ptr<Any>& value(Node& a) noexcept{
    return a._value;
}
inline bool contains(const Node& a, const AtomName& name) noexcept{
    return a.contains(name);
}
inline Node& add(Node& a, Node&& to_child){
    assert (! contains(a, name(to_child)));
    const auto [it,_] = a._childs.emplace(name(to_child), std::move(to_child));
    return it->second;
}
inline Node& add(Node& a, const AtomName& name){
    return add(a, Node(name));
}
inline Node& operator<<(Node& a, const AtomName& name){
    add(a, name);
    return a;
}
inline Node& operator<<(Node& a, Node&& to_child){
    add(a, std::move(to_child));
    return a;
}
inline ostream& operator<<(ostream& out, const Node& n){
    n.ToString(out);
    return out;
}

struct Document : Node{
    explicit Document():Node(consts::DEFAULT_DOCUMENT_NAME){}
    explicit Document(string_view name):Node(AtomName(name)){}
    explicit Document(const string& name):Node(AtomName(name)){}
};
ostream& operator<<(ostream& str, const Document& doc){
    for (const auto& it : doc){
        str << it.second;
    }
    return str;
}
Binary& operator<<(Binary& bin, const Document& doc){
    /*bin << consts::BINARY_HEAD;
    for (const auto& it : doc){
        bin << it.second;
    }*/
    return bin;
}
void format(ostream& out, const Node& n, uint indent){
    auto tab = indent+1;
    while (--tab > 0){out << ' ';}
    out << name(n) << ' ';
    if (value(n) != Undefined){out << *(value(n));}
    for (const auto& it : n){
        format((out << '\n'), it.second, indent+2);
    }
}
string format(const Document& doc){
    stringstream ss;
    format(ss, doc, 0);
    return ss.str();
}}
